/*******************************************************************************
 * Copyright 2011 Towee.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

package net.towee.server.persistence.impl;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Properties;

import net.towee.server.exceptions.PersistenceStartException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.morphia.logging.MorphiaLoggerFactory;
import com.google.code.morphia.logging.slf4j.SLF4JLogrImplFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.MongoException;

/**
 * Class to lazily setup and manage the MongoDb connection.
 * 
 * @author c58
 * 
 */
public class MongoDbProvider {
	private static final Logger LOG =  LoggerFactory.getLogger(MongoDbProvider.class);

	/** Location of the MongoDB properties file in the classpath. */
	private static final String PROPERTIES_LOC = "net/towee/server/mongodb.properties";

	/** Name of the property that stores the host. */
	private static final String HOST_PROPERTY = "mongoDbHost";

	/** Name of the property that stores the port. */
	private static final String PORT_PROPERTY = "mongoDbPort";

	/** Name of the property that stores the name of the database. */
	private static final String DATABASE_NAME_PROPERTY = "mongoDbDatabase";

	/**
	 * Our {@link Mongo} instance, should be accessed by getMongo unless during
	 * start().
	 */
	private Mongo mongo;

	/**
	 * Our lazily loaded {@link Properties} instance.
	 */
	private Properties properties;

	/**
	 * Lazily instantiated {@link MongoDbStore} .
	 */
	private MongoDbStore mongoDbStore;

	/**
	 * Stores whether we have successfully setup a live {@link Mongo} instance.
	 */
	private boolean isRunning;
	private boolean debug;

	/**
	 * Constructs a new empty {@link MongoDbProvider}.
	 * @param debug 
	 */
	public MongoDbProvider(boolean debug) {
		this.debug = debug;
		
		// Set logging system as slf4j
		MorphiaLoggerFactory.registerLogger(SLF4JLogrImplFactory.class);
	}

	/**
	 * Starts the {@link Mongo} instance and explicitly checks whether it is
	 * actually alive.
	 * 
	 * @throws PersistenceStartException
	 *             if we can't make a connection to MongoDb.
	 */
	private void start() {
		Preconditions.checkState(!isRunning(),
				"Can't start after a connection has been established");

		ensurePropertiesLoaded();

		String host = properties.getProperty(HOST_PROPERTY);
		int port = Integer.parseInt(properties.getProperty(PORT_PROPERTY));
		try {
			mongo = new Mongo(host, port);
		} catch (UnknownHostException e) {
			throw new PersistenceStartException(
					"Unable to resolve the MongoDb hostname", e);
		}

		try {
			if(debug)
				mongo.dropDatabase( getDatabaseName() );
			
			// Check to see if we are alive
			mongo.getDB(getDatabaseName()).command("ping");
		} catch (MongoException e) {
			throw new PersistenceStartException("Can't ping MongoDb", e);
		}

		isRunning = true;
		LOG.info("Started MongoDb persistence");
	}

	/**
	 * Ensures that the properties for MongoDb are loaded.
	 * 
	 * @throws PersistenceStartException
	 *             if the properties can not be loaded.
	 */
	private void ensurePropertiesLoaded() {
		if (properties != null) {
			// Already loaded
			return;
		}
		Properties properties = new Properties();
		try {
			properties.load(ClassLoader
					.getSystemResourceAsStream(PROPERTIES_LOC));
		} catch (IOException e) {
			throw new PersistenceStartException(
					"Unable to load Properties for MongoDb", e);
		}
		this.properties = properties;
	}

	/**
	 * Returns the name of the database as specified in the properties file.
	 */
	private String getDatabaseName() {
		ensurePropertiesLoaded();
		return properties.getProperty(DATABASE_NAME_PROPERTY);
	}

	/**
	 * Returns a {@link DB} instance for the database with the given name
	 * 
	 * @param name
	 *            the name of the database
	 */
	@VisibleForTesting
	DB getDatabaseForName(String name) {
		return getMongo().getDB(name);
	}

	/**
	 * Return the {@link Mongo} instance that we are managing.
	 */
	private Mongo getMongo() {
		if (!isRunning) {
			start();
		}
		return mongo;
	}

	/**
	 * Returns true if the {@link MongoDbProvider} is running.
	 */
	public boolean isRunning() {
		return isRunning;
	}

	/**
	 * Returns a {@link MongoDbStore} instance created from the settings in this
	 * provider.
	 */
	public MongoDbStore provideMongoDbStore() {
		if (mongoDbStore == null) {
			mongoDbStore = new MongoDbStore(getMongo(), getDatabaseName());
		}
		return mongoDbStore;
	}
}
